package cn.wen.yinxiu.order.service.impl;

import cn.wen.yinxiu.common.cache.DistributedCache;
import cn.wen.yinxiu.common.constant.Constant;
import cn.wen.yinxiu.common.constant.OrderStatusEnum;
import cn.wen.yinxiu.common.constant.RedisConstants;
import cn.wen.yinxiu.common.convention.errorcode.ErrorCode;
import cn.wen.yinxiu.common.convention.exception.ServiceException;
import cn.wen.yinxiu.common.designpattern.stratey.AbstractStrategyChoose;
import cn.wen.yinxiu.common.to.product.OrderItemTO;
import cn.wen.yinxiu.common.to.product.SkuInfoTO;
import cn.wen.yinxiu.common.to.product.SkuLockTO;
import cn.wen.yinxiu.common.to.user.FareTO;
import cn.wen.yinxiu.common.to.user.UserAddressTO;
import cn.wen.yinxiu.common.to.user.UserTO;
import cn.wen.yinxiu.common.toolkit.BeanUtil;
import cn.wen.yinxiu.common.toolkit.StringUtils;
import cn.wen.yinxiu.order.entity.OrderItemEntity;
import cn.wen.yinxiu.order.feign.ProductFeignService;
import cn.wen.yinxiu.order.feign.UserFeignService;
import cn.wen.yinxiu.order.service.OrderItemService;
import cn.wen.yinxiu.order.vo.*;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import cn.wen.yinxiu.common.toolkit.PageUtils;
import cn.wen.yinxiu.common.toolkit.Query;

import cn.wen.yinxiu.order.dao.OrderDao;
import cn.wen.yinxiu.order.entity.OrderEntity;
import cn.wen.yinxiu.order.service.OrderService;
import org.springframework.transaction.annotation.Transactional;


@Service("orderService")
public class OrderServiceImpl extends ServiceImpl<OrderDao, OrderEntity> implements OrderService {

    private ThreadLocal<OrderSubmitVO> confirmVoThreadLocal = new ThreadLocal<>();

    @Autowired
    private UserFeignService userFeignService;

    @Autowired
    private ProductFeignService productFeignService;

    @Autowired
    private ThreadPoolExecutor executor;

    @Autowired
    private DistributedCache distributedCache;

    @Autowired
    private OrderItemService orderItemService;

    @Autowired
    private OrderDao orderDao;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private AbstractStrategyChoose abstractStrategyChoose;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<OrderEntity> page = this.page(
                new Query<OrderEntity>().getPage(params),
                new QueryWrapper<OrderEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    public OrderConfirmVo confirmOrder(Long userId, Long skuId) {
        // 构建OrderConfirmVo
        OrderConfirmVo confirmVo = new OrderConfirmVo();

        // 开启第一个异步任务
        CompletableFuture<Void> addressFuture = CompletableFuture.runAsync(() -> {

            //1、远程查询所有的收获地址列表
            List<UserAddressTO> address = userFeignService.getAddress(userId).getData();
            confirmVo.setUserAddressList(address);
        }, executor);

        // 开启第二个异步任务
        CompletableFuture<Void> stockInfoFuture = CompletableFuture.runAsync(() -> {
            // 远程查询商品库存信息
            boolean hasStock = productFeignService.getSkuHasStock(skuId).getData();
            confirmVo.setHasStock(hasStock);
        });

        // 4、价格数据自动计算

        // TODO 5、防重令牌(防止表单重复提交)
        // 为用户设置一个token，三十分钟过期时间（存在redis）
        String token = UUID.randomUUID().toString().replace("-", "");
        distributedCache.put(RedisConstants.USER_ORDER_TOKEN_PREFIX + Constant.SYMBOL_COLON + userId, token, 30, TimeUnit.MINUTES);
        confirmVo.setOrderToken(token);
        try {
            CompletableFuture.allOf(addressFuture, stockInfoFuture).get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        return confirmVo;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public SubmitOrderResponseVO submitOrder(OrderSubmitVO vo) {
        confirmVoThreadLocal.set(vo);

        SubmitOrderResponseVO responseVo = new SubmitOrderResponseVO();
        // 去创建、下订单、验令牌、验价格、锁定库存...

        // 获取当前用户登录的信息
        Long userId = vo.getUserId();

        responseVo.setCode(0);

        // 1、验证令牌是否合法【令牌的对比和删除必须保证原子性】
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        String orderToken = vo.getOrderToken();

        // 通过Lua脚本原子验证令牌和删除令牌
        Long result = distributedCache.execute(new DefaultRedisScript<Long>(script, Long.class),
                Arrays.asList(RedisConstants.USER_ORDER_TOKEN_PREFIX + Constant.SYMBOL_COLON + userId),
                orderToken);

        if (result == 0L) {
            // 令牌验证失败
            responseVo.setCode(1);
            return responseVo;
        } else {
            // 令牌验证成功
            // 1、创建订单、订单项等信息
            OrderCreateTo order = createOrder(vo.getUserId(), vo.getSkuId());
            // 获取商品信息
            SkuInfoTO skuInfo = productFeignService.getSkuInfoById(vo.getSkuId()).getData();
            // 2、验证价格
            BigDecimal payAmount = order.getOrder().getPayAmount();
            BigDecimal payPrice = vo.getPayPrice();

            if (Math.abs(payAmount.subtract(payPrice).doubleValue()) < 0.01) {
                // 金额对比
                // TODO 3、保存订单
                saveOrder(order);

                // 4、库存锁定,只要有异常，回滚订单数据
                // 订单号、所有订单项信息(skuId, skuNum, skuName)
                SkuLockTO lockTO = new SkuLockTO();
                lockTO.setOrderSn(order.getOrder().getOrderSn());

                // 获取出要锁定的商品数据信息
                OrderItemTO orderItemTO = new OrderItemTO();
                orderItemTO.setSkuId(vo.getSkuId());
                orderItemTO.setCount(vo.getCount());
                orderItemTO.setTitle(skuInfo.getSkuName());
                lockTO.setLocks(orderItemTO);

                // TODO 调用远程锁定库存的方法
                // 出现的问题：扣减库存成功了，但是由于网络原因超时，出现异常，导致订单事务回滚，库存事务不回滚(解决方案：seata)
                // 为了保证高并发，不推荐使用seata，因为是加锁，并行化，提升不了效率,可以发消息给库存服务
                boolean hasStock = productFeignService.orderLockStock(lockTO).getData();
                if (hasStock) {
                    // 锁定成功
                    responseVo.setOrder(order.getOrder());
                    // int i = 10/0;

                    // TODO 订单创建成功，发送消息给MQ
                    rabbitTemplate.convertAndSend("order-event-exchange", "order.create.order", order.getOrder());

                    return responseVo;
                } else {
                    // 锁定失败
                    throw new ServiceException(ErrorCode.SKU_UN_LOCKED_STOCK_FAILURE);
                    // responseVo.setCode(3);
                    // return responseVo;
                }

            } else {
                responseVo.setCode(2);
                return responseVo;
            }
        }
    }

    @Override
    public OrderEntity getOrderByOrderSn(String orderSn) {
        return orderDao.selectOne(new QueryWrapper<OrderEntity>().eq("order_sn", orderSn));
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void callbackPay(Pay payAggregateRoot) {

    }

    @Override
    public String payCommonService(PayRequest requestParam) {
        PayRequest pay =  createPay(BeanUtil.convert(requestParam, Pay.class));
        // 策略模式：通过策略模式封装支付渠道和支付场景，用户支付时动态选择对应的支付组件
        PayResponse result = abstractStrategyChoose.chooseAndExecuteResp(requestParam.buildMark(), pay);
        return result.getBody();
    }

    private PayRequest createPay(Pay convert) {
        PayCommand payRequest = new PayCommand();
        String orderSn = convert.getOrderSn();
        OrderEntity orderInfo = this.getOrderByOrderSn(orderSn);

        // 保留两位小数点，向上取值
        BigDecimal payAmount = orderInfo.getPayAmount().setScale(2, BigDecimal.ROUND_UP);
        payRequest.setTotalAmount(payAmount.toString());
        payRequest.setOutOrderSn(orderInfo.getOrderSn());

        // 查询订单项的数据
        List<OrderItemEntity> orderItemInfo = orderItemService.list(
                new QueryWrapper<OrderItemEntity>().eq("order_sn", orderSn));
        OrderItemEntity orderItemEntity = orderItemInfo.get(0);

        payRequest.setSubject(orderItemEntity.getSkuName());
        return payRequest;
    }

    /**
     * 保存订单所有数据
     *
     * @param orderCreateTo
     */
    private void saveOrder(OrderCreateTo orderCreateTo) {

        // 获取订单信息
        OrderEntity order = orderCreateTo.getOrder();
        order.setModifyTime(new Date());
        order.setCreateTime(new Date());
        // 保存订单
        this.baseMapper.insert(order);

        // 获取订单项信息
        OrderItemEntity orderItem = orderCreateTo.getOrderItem();
        // 批量保存订单项数据
        orderItemService.save(orderItem);
    }

    private OrderCreateTo createOrder(Long userId, Long skuId) {

        OrderCreateTo createTo = new OrderCreateTo();

        // 1、生成订单号
        String orderSn = IdWorker.getTimeId();
        OrderEntity orderEntity = builderOrder(orderSn, userId);

        // 2、获取的订单项
        OrderItemEntity orderItemEntity = builderOrderItem(orderSn, skuId);

        // 3、验价(计算价格、积分等信息)
        computePrice(orderEntity, orderItemEntity);

        createTo.setOrder(orderEntity);
        createTo.setOrderItem(orderItemEntity);

        return createTo;
    }

    /**
     * 计算价格的方法
     *
     * @param orderEntity
     * @param orderItem
     */
    private void computePrice(OrderEntity orderEntity, OrderItemEntity orderItem) {

        // 总价
        BigDecimal total = new BigDecimal("0.0");
        // 优惠价
        BigDecimal coupon = new BigDecimal("0.0");
        BigDecimal intergration = new BigDecimal("0.0");
        BigDecimal promotion = new BigDecimal("0.0");

        // 积分、成长值
        Integer integrationTotal = 0;
        Integer growthTotal = 0;

        // 优惠价格信息
        coupon = coupon.add(orderItem.getCouponAmount());
        promotion = promotion.add(orderItem.getPromotionAmount());
        intergration = intergration.add(orderItem.getIntegrationAmount());

        // 总价
        total = total.add(orderItem.getRealAmount());

        //积分信息和成长值信息
        integrationTotal += orderItem.getGiftIntegration();
        growthTotal += orderItem.getGiftGrowth();

        // 1、订单价格相关的
        orderEntity.setTotalAmount(total);
        // 设置应付总额(总额+运费)
        orderEntity.setPayAmount(total.add(orderEntity.getFreightAmount()));
        orderEntity.setCouponAmount(coupon);
        orderEntity.setPromotionAmount(promotion);
        orderEntity.setIntegrationAmount(intergration);

        // 设置积分成长值信息
        orderEntity.setIntegration(integrationTotal);
        orderEntity.setGrowth(growthTotal);

        // 设置删除状态(0-未删除，1-已删除)
        orderEntity.setDeleteStatus(0);
    }

    /**
     * 构建某一个订单项的数据
     *
     * @return
     */
    private OrderItemEntity builderOrderItem(String orderSn, Long skuId) {
        OrderSubmitVO orderSubmitVO = confirmVoThreadLocal.get();
        // 获取指定的sku
        SkuInfoTO item = productFeignService.getSkuInfoById(skuId).getData();
        OrderItemEntity orderItemEntity = new OrderItemEntity();
        //2、商品的sku信息
        orderItemEntity.setSkuId(skuId);
        orderItemEntity.setSkuName(item.getSkuTitle());
        if (StringUtils.isNotEmpty(item.getImageUrl())) {
            orderItemEntity.setSkuPicture(item.getImageUrl().split(";")[0]);
        }
        orderItemEntity.setSkuPrice(item.getPrice());
        orderItemEntity.setSkuQuantity(orderSubmitVO.getCount());

        // 3、订单项的价格信息
        orderItemEntity.setPromotionAmount(BigDecimal.ZERO);
        orderItemEntity.setCouponAmount(BigDecimal.ZERO);
        orderItemEntity.setIntegrationAmount(BigDecimal.ZERO);

        // 当前订单项的实际金额.总额 - 各种优惠价格
        // 原来的价格
        BigDecimal origin = orderItemEntity.getSkuPrice().multiply(new BigDecimal(orderItemEntity.getSkuQuantity().toString()));
        // 原价减去优惠价得到最终的价格
        BigDecimal subtract = origin.subtract(orderItemEntity.getCouponAmount())
                .subtract(orderItemEntity.getPromotionAmount())
                .subtract(orderItemEntity.getIntegrationAmount());
        orderItemEntity.setRealAmount(subtract);

        return orderItemEntity;

    }


    /**
     * 构建订单信息
     *
     * @param orderSn
     * @return
     */
    private OrderEntity builderOrder(String orderSn, Long userId) {

        OrderEntity orderEntity = new OrderEntity();
        orderEntity.setUserId(userId);
        // 获取用户信息
        UserTO userInfo = userFeignService.getUserInfoById(userId).getData();
        orderEntity.setOrderSn(orderSn);
        orderEntity.setUsername(userInfo.getUsername());

        OrderSubmitVO orderSubmitVo = confirmVoThreadLocal.get();

        // 远程获取收货地址和运费信息
        FareTO fareAddressVo = userFeignService.getFare(orderSubmitVo.getAddrId()).getData();

        // 获取到运费信息
        BigDecimal fare = fareAddressVo.getFare();
        orderEntity.setFreightAmount(fare);

        // 获取到收货地址信息
        UserAddressTO address = fareAddressVo.getAddress();
        //设置收货人信息
        orderEntity.setReceiverName(address.getReceiverName());
        orderEntity.setReceiverPhone(address.getReceiverPhone());
        orderEntity.setReceiverPostCode(address.getPostalCode());
        orderEntity.setReceiverProvince(address.getReceiverProvince());
        orderEntity.setReceiverCity(address.getReceiverCity());
        orderEntity.setReceiverRegion(address.getReceiverDistrict());
        orderEntity.setReceiverDetailAddress(address.getReceiverAddress());

        // 设置订单相关的状态信息
        orderEntity.setStatus(OrderStatusEnum.CREATE_NEW.getCode());
        orderEntity.setAutoConfirmDay(7);
        orderEntity.setConfirmStatus(0);
        return orderEntity;
    }

}